/* 
 *
 * This source code file contains modifications made by THL A29 Limited ("Tencent Modifications").
 * All Tencent Modifications are Copyright (C) 2023 THL A29 Limited.
 * 
 * src/interfaces/ecpg/ecpglib/data.c
 * 
 */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include <float.h>
#include <math.h>

#include "ecpgtype.h"
#include "ecpglib.h"
#include "ecpgerrno.h"
#include "extern.h"
#include "sqlca.h"
#include "pgtypes_numeric.h"
#include "pgtypes_date.h"
#include "pgtypes_timestamp.h"
#include "pgtypes_interval.h"

/* returns true if character c is a delimiter for the given array type */
static bool
array_delimiter(enum ARRAY_TYPE isarray, char c)
{
    if (isarray == ECPG_ARRAY_ARRAY && c == ',')
        return true;

    if (isarray == ECPG_ARRAY_VECTOR && c == ' ')
        return true;

    return false;
}

/* returns true if character c marks the boundary for the given array type */
static bool
array_boundary(enum ARRAY_TYPE isarray, char c)
{
    if (isarray == ECPG_ARRAY_ARRAY && c == '}')
        return true;

    if (isarray == ECPG_ARRAY_VECTOR && c == '\0')
        return true;

    return false;
}

/* returns true if some garbage is found at the end of the scanned string */
static bool
garbage_left(enum ARRAY_TYPE isarray, char *scan_length, enum COMPAT_MODE compat)
{
    /*
     * INFORMIX allows for selecting a numeric into an int, the result is
     * truncated
     */
    if (isarray == ECPG_ARRAY_NONE)
    {
        if (INFORMIX_MODE(compat) && *scan_length == '.')
            return false;

        if (*scan_length != ' ' && *scan_length != '\0')
            return true;
    }
    else if (ECPG_IS_ARRAY(isarray) && !array_delimiter(isarray, *scan_length) && !array_boundary(isarray, *scan_length))
        return true;

    return false;
}

/* stolen code from src/backend/utils/adt/float.c */
#if defined(WIN32) && !defined(NAN)
static const uint32 nan[2] = {0xffffffff, 0x7fffffff};

#define NAN (*(const double *) nan)
#endif

static double
get_float8_infinity(void)
{
#ifdef INFINITY
    return (double) INFINITY;
#else
    return (double) (HUGE_VAL * HUGE_VAL);
#endif
}

static double
get_float8_nan(void)
{
    /* (double) NAN doesn't work on some NetBSD/MIPS releases */
#if defined(NAN) && !(defined(__NetBSD__) && defined(__mips__))
    return (double) NAN;
#else
    return (double) (0.0 / 0.0);
#endif
}

static bool
check_special_value(char *ptr, double *retval, char **endptr)
{
    if (pg_strncasecmp(ptr, "NaN", 3) == 0)
    {
        *retval = get_float8_nan();
        *endptr = ptr + 3;
        return true;
    }
    else if (pg_strncasecmp(ptr, "Infinity", 8) == 0)
    {
        *retval = get_float8_infinity();
        *endptr = ptr + 8;
        return true;
    }
    else if (pg_strncasecmp(ptr, "-Infinity", 9) == 0)
    {
        *retval = -get_float8_infinity();
        *endptr = ptr + 9;
        return true;
    }

    return false;
}

bool
ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
              enum ECPGttype type, enum ECPGttype ind_type,
              char *var, char *ind, long varcharsize, long offset,
              long ind_offset, enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
{// #lizard forgives
    struct sqlca_t *sqlca = ECPGget_sqlca();
    char       *pval = (char *) PQgetvalue(results, act_tuple, act_field);
    int            binary = PQfformat(results, act_field);
    int            size = PQgetlength(results, act_tuple, act_field);
    int            value_for_indicator = 0;
    long        log_offset;

    if (sqlca == NULL)
    {
        ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
                   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
        return (false);
    }

    /*
     * If we are running in a regression test, do not log the offset variable,
     * it depends on the machine's alignment.
     */
    if (ecpg_internal_regression_mode)
        log_offset = -1;
    else
        log_offset = offset;

    ecpg_log("ecpg_get_data on line %d: RESULT: %s offset: %ld; array: %s\n", lineno, pval ? (binary ? "BINARY" : pval) : "EMPTY", log_offset, ECPG_IS_ARRAY(isarray) ? "yes" : "no");

    /* pval is a pointer to the value */
    if (!pval)
    {
        /*
         * This should never happen because we already checked that we found
         * at least one tuple, but let's play it safe.
         */
        ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
        return (false);
    }

    /* We will have to decode the value */

    /*
     * check for null value and set indicator accordingly, i.e. -1 if NULL and
     * 0 if not
     */
    if (PQgetisnull(results, act_tuple, act_field))
        value_for_indicator = -1;

    switch (ind_type)
    {
        case ECPGt_short:
        case ECPGt_unsigned_short:
            *((short *) (ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
        case ECPGt_int:
        case ECPGt_unsigned_int:
            *((int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
        case ECPGt_long:
        case ECPGt_unsigned_long:
            *((long *) (ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
#ifdef HAVE_LONG_LONG_INT
        case ECPGt_long_long:
        case ECPGt_unsigned_long_long:
            *((long long int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
#endif                            /* HAVE_LONG_LONG_INT */
        case ECPGt_NO_INDICATOR:
            if (value_for_indicator == -1)
            {
                if (force_indicator == false)
                {
                    /*
                     * Informix has an additional way to specify NULLs note
                     * that this uses special values to denote NULL
                     */
                    ECPGset_noind_null(type, var + offset * act_tuple);
                }
                else
                {
                    ecpg_raise(lineno, ECPG_MISSING_INDICATOR,
                               ECPG_SQLSTATE_NULL_VALUE_NO_INDICATOR_PARAMETER,
                               NULL);
                    return (false);
                }
            }
            break;
        default:
            ecpg_raise(lineno, ECPG_UNSUPPORTED,
                       ECPG_SQLSTATE_ECPG_INTERNAL_ERROR,
                       ecpg_type_name(ind_type));
            return (false);
            break;
    }

    if (value_for_indicator == -1)
        return (true);

    /* let's check if it really is an array if it should be one */
    if (isarray == ECPG_ARRAY_ARRAY)
    {
        if (*pval != '{')
        {
            ecpg_raise(lineno, ECPG_DATA_NOT_ARRAY,
                       ECPG_SQLSTATE_DATATYPE_MISMATCH, NULL);
            return (false);
        }

        switch (type)
        {
            case ECPGt_char:
            case ECPGt_unsigned_char:
            case ECPGt_varchar:
            case ECPGt_string:
                break;

            default:
                pval++;
                break;
        }
    }

    do
    {
        if (binary)
        {
            if (varcharsize == 0 || varcharsize * offset >= size)
                memcpy(var + offset * act_tuple, pval, size);
            else
            {
                memcpy(var + offset * act_tuple, pval, varcharsize * offset);

                if (varcharsize * offset < size)
                {
                    /* truncation */
                    switch (ind_type)
                    {
                        case ECPGt_short:
                        case ECPGt_unsigned_short:
                            *((short *) (ind + ind_offset * act_tuple)) = size;
                            break;
                        case ECPGt_int:
                        case ECPGt_unsigned_int:
                            *((int *) (ind + ind_offset * act_tuple)) = size;
                            break;
                        case ECPGt_long:
                        case ECPGt_unsigned_long:
                            *((long *) (ind + ind_offset * act_tuple)) = size;
                            break;
#ifdef HAVE_LONG_LONG_INT
                        case ECPGt_long_long:
                        case ECPGt_unsigned_long_long:
                            *((long long int *) (ind + ind_offset * act_tuple)) = size;
                            break;
#endif                            /* HAVE_LONG_LONG_INT */
                        default:
                            break;
                    }
                    sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
                }
            }
            pval += size;
        }
        else
        {
            switch (type)
            {
                    long        res;
                    unsigned long ures;
                    double        dres;
                    char       *scan_length;
                    numeric    *nres;
                    date        ddres;
                    timestamp    tres;
                    interval   *ires;
                    char       *endptr,
                                endchar;

                case ECPGt_short:
                case ECPGt_int:
                case ECPGt_long:
                    res = strtol(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat))
                    {
                        ecpg_raise(lineno, ECPG_INT_FORMAT,
                                   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type)
                    {
                        case ECPGt_short:
                            *((short *) (var + offset * act_tuple)) = (short) res;
                            break;
                        case ECPGt_int:
                            *((int *) (var + offset * act_tuple)) = (int) res;
                            break;
                        case ECPGt_long:
                            *((long *) (var + offset * act_tuple)) = (long) res;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

                case ECPGt_unsigned_short:
                case ECPGt_unsigned_int:
                case ECPGt_unsigned_long:
                    ures = strtoul(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat))
                    {
                        ecpg_raise(lineno, ECPG_UINT_FORMAT,
                                   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type)
                    {
                        case ECPGt_unsigned_short:
                            *((unsigned short *) (var + offset * act_tuple)) = (unsigned short) ures;
                            break;
                        case ECPGt_unsigned_int:
                            *((unsigned int *) (var + offset * act_tuple)) = (unsigned int) ures;
                            break;
                        case ECPGt_unsigned_long:
                            *((unsigned long *) (var + offset * act_tuple)) = (unsigned long) ures;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

#ifdef HAVE_LONG_LONG_INT
#ifdef HAVE_STRTOLL
                case ECPGt_long_long:
                    *((long long int *) (var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat))
                    {
                        ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    break;
#endif                            /* HAVE_STRTOLL */
#ifdef HAVE_STRTOULL
                case ECPGt_unsigned_long_long:
                    *((unsigned long long int *) (var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat))
                    {
                        ecpg_raise(lineno, ECPG_UINT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    break;
#endif                            /* HAVE_STRTOULL */
#endif                            /* HAVE_LONG_LONG_INT */

                case ECPGt_float:
                case ECPGt_double:
                    if (isarray && *pval == '"')
                        pval++;

                    if (!check_special_value(pval, &dres, &scan_length))
                        dres = strtod(pval, &scan_length);

                    if (isarray && *scan_length == '"')
                        scan_length++;

                    if (garbage_left(isarray, scan_length, compat))
                    {
                        ecpg_raise(lineno, ECPG_FLOAT_FORMAT,
                                   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type)
                    {
                        case ECPGt_float:
                            *((float *) (var + offset * act_tuple)) = dres;
                            break;
                        case ECPGt_double:
                            *((double *) (var + offset * act_tuple)) = dres;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

                case ECPGt_bool:
                    if (pval[0] == 'f' && pval[1] == '\0')
                    {
                        *((bool *) (var + offset * act_tuple)) = false;
                        pval++;
                        break;
                    }
                    else if (pval[0] == 't' && pval[1] == '\0')
                    {
                        *((bool *) (var + offset * act_tuple)) = true;
                        pval++;
                        break;
                    }
                    else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field))
                    {
                        /* NULL is valid */
                        break;
                    }

                    ecpg_raise(lineno, ECPG_CONVERT_BOOL,
                               ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                    return (false);
                    break;

                case ECPGt_char:
                case ECPGt_unsigned_char:
                case ECPGt_string:
                    {
                        char       *str = (char *) (var + offset * act_tuple);

                        /*
                         * If varcharsize is unknown and the offset is that of
                         * char *, then this variable represents the array of
                         * character pointers. So, use extra indirection.
                         */
                        if (varcharsize == 0 && offset == sizeof(char *))
                            str = *(char **) str;

                        if (varcharsize == 0 || varcharsize > size)
                        {
                            strncpy(str, pval, size + 1);
                            /* do the rtrim() */
                            if (type == ECPGt_string)
                            {
                                char       *last = str + size;

                                while (last > str && (*last == ' ' || *last == '\0'))
                                {
                                    *last = '\0';
                                    last--;
                                }
                            }
                        }
                        else
                        {
                            strncpy(str, pval, varcharsize);

                            if (varcharsize < size)
                            {
                                /* truncation */
                                switch (ind_type)
                                {
                                    case ECPGt_short:
                                    case ECPGt_unsigned_short:
                                        *((short *) (ind + ind_offset * act_tuple)) = size;
                                        break;
                                    case ECPGt_int:
                                    case ECPGt_unsigned_int:
                                        *((int *) (ind + ind_offset * act_tuple)) = size;
                                        break;
                                    case ECPGt_long:
                                    case ECPGt_unsigned_long:
                                        *((long *) (ind + ind_offset * act_tuple)) = size;
                                        break;
#ifdef HAVE_LONG_LONG_INT
                                    case ECPGt_long_long:
                                    case ECPGt_unsigned_long_long:
                                        *((long long int *) (ind + ind_offset * act_tuple)) = size;
                                        break;
#endif                            /* HAVE_LONG_LONG_INT */
                                    default:
                                        break;
                                }
                                sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
                            }
                        }
                        pval += size;
                    }
                    break;

                case ECPGt_varchar:
                    {
                        struct ECPGgeneric_varchar *variable =
                        (struct ECPGgeneric_varchar *) (var + offset * act_tuple);

                        variable->len = size;
                        if (varcharsize == 0)
                            strncpy(variable->arr, pval, variable->len);
                        else
                        {
                            strncpy(variable->arr, pval, varcharsize);

                            if (variable->len > varcharsize)
                            {
                                /* truncation */
                                switch (ind_type)
                                {
                                    case ECPGt_short:
                                    case ECPGt_unsigned_short:
                                        *((short *) (ind + ind_offset * act_tuple)) = variable->len;
                                        break;
                                    case ECPGt_int:
                                    case ECPGt_unsigned_int:
                                        *((int *) (ind + ind_offset * act_tuple)) = variable->len;
                                        break;
                                    case ECPGt_long:
                                    case ECPGt_unsigned_long:
                                        *((long *) (ind + ind_offset * act_tuple)) = variable->len;
                                        break;
#ifdef HAVE_LONG_LONG_INT
                                    case ECPGt_long_long:
                                    case ECPGt_unsigned_long_long:
                                        *((long long int *) (ind + ind_offset * act_tuple)) = variable->len;
                                        break;
#endif                            /* HAVE_LONG_LONG_INT */
                                    default:
                                        break;
                                }
                                sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';

                                variable->len = varcharsize;
                            }
                        }
                        pval += size;
                    }
                    break;

                case ECPGt_decimal:
                case ECPGt_numeric:
                    for (endptr = pval; *endptr && *endptr != ',' && *endptr != '}'; endptr++);
                    endchar = *endptr;
                    *endptr = '\0';
                    nres = PGTYPESnumeric_from_asc(pval, &scan_length);
                    *endptr = endchar;

                    /* did we get an error? */
                    if (nres == NULL)
                    {
                        ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
                                 lineno, pval, errno);

                        if (INFORMIX_MODE(compat))
                        {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            nres = PGTYPESnumeric_new();
                            if (nres)
                                ECPGset_noind_null(ECPGt_numeric, nres);
                            else
                            {
                                ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
                                           ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
                                return (false);
                            }
                        }
                        else
                        {
                            ecpg_raise(lineno, ECPG_NUMERIC_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    else
                    {
                        if (!isarray && garbage_left(isarray, scan_length, compat))
                        {
                            free(nres);
                            ecpg_raise(lineno, ECPG_NUMERIC_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    pval = scan_length;

                    if (type == ECPGt_numeric)
                        PGTYPESnumeric_copy(nres, (numeric *) (var + offset * act_tuple));
                    else
                        PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * act_tuple));

                    PGTYPESnumeric_free(nres);
                    break;

                case ECPGt_interval:
                    if (*pval == '"')
                        pval++;

                    for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
                    endchar = *endptr;
                    *endptr = '\0';
                    ires = PGTYPESinterval_from_asc(pval, &scan_length);
                    *endptr = endchar;

                    /* did we get an error? */
                    if (ires == NULL)
                    {
                        ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
                                 lineno, pval, errno);

                        if (INFORMIX_MODE(compat))
                        {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ires = (interval *) ecpg_alloc(sizeof(interval), lineno);
                            if (!ires)
                                return (false);

                            ECPGset_noind_null(ECPGt_interval, ires);
                        }
                        else
                        {
                            ecpg_raise(lineno, ECPG_INTERVAL_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    else
                    {
                        if (*scan_length == '"')
                            scan_length++;

                        if (!isarray && garbage_left(isarray, scan_length, compat))
                        {
                            free(ires);
                            ecpg_raise(lineno, ECPG_INTERVAL_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    pval = scan_length;

                    PGTYPESinterval_copy(ires, (interval *) (var + offset * act_tuple));
                    free(ires);
                    break;

                case ECPGt_date:
                    if (*pval == '"')
                        pval++;

                    for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
                    endchar = *endptr;
                    *endptr = '\0';
                    ddres = PGTYPESdate_from_asc(pval, &scan_length);
                    *endptr = endchar;

                    /* did we get an error? */
                    if (errno != 0)
                    {
                        ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
                                 lineno, pval, errno);

                        if (INFORMIX_MODE(compat))
                        {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ECPGset_noind_null(ECPGt_date, &ddres);
                        }
                        else
                        {
                            ecpg_raise(lineno, ECPG_DATE_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    else
                    {
                        if (*scan_length == '"')
                            scan_length++;

                        if (!isarray && garbage_left(isarray, scan_length, compat))
                        {
                            ecpg_raise(lineno, ECPG_DATE_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }

                    *((date *) (var + offset * act_tuple)) = ddres;
                    pval = scan_length;
                    break;

                case ECPGt_timestamp:
                    if (*pval == '"')
                        pval++;

                    for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
                    endchar = *endptr;
                    *endptr = '\0';
                    tres = PGTYPEStimestamp_from_asc(pval, &scan_length);
                    *endptr = endchar;

                    /* did we get an error? */
                    if (errno != 0)
                    {
                        ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
                                 lineno, pval, errno);

                        if (INFORMIX_MODE(compat))
                        {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ECPGset_noind_null(ECPGt_timestamp, &tres);
                        }
                        else
                        {
                            ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    else
                    {
                        if (*scan_length == '"')
                            scan_length++;

                        if (!isarray && garbage_left(isarray, scan_length, compat))
                        {
                            ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT,
                                       ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }

                    *((timestamp *) (var + offset * act_tuple)) = tres;
                    pval = scan_length;
                    break;

                default:
                    ecpg_raise(lineno, ECPG_UNSUPPORTED,
                               ECPG_SQLSTATE_ECPG_INTERNAL_ERROR,
                               ecpg_type_name(type));
                    return (false);
                    break;
            }
            if (ECPG_IS_ARRAY(isarray))
            {
                bool        string = false;

                /* set array to next entry */
                ++act_tuple;

                /* set pval to the next entry */

                /*
                 * *pval != '\0' should not be needed, but is used as a safety
                 * guard
                 */
                for (; *pval != '\0' && (string || (!array_delimiter(isarray, *pval) && !array_boundary(isarray, *pval))); ++pval)
                    if (*pval == '"')
                        string = string ? false : true;

                if (array_delimiter(isarray, *pval))
                    ++pval;
            }
        }
    } while (*pval != '\0' && !array_boundary(isarray, *pval));

    return (true);
}
